﻿using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Drawing;
using System.Text;
using System.Threading;
using System.Windows;
using Microsoft.Win32.SafeHandles;
using STP;
using System.ComponentModel;
using System.Xml.Linq;
using System.Runtime.InteropServices;
using System.Collections.ObjectModel;
using System.IO;

namespace Simulator
{
	public class Bridge : BridgeWrapper.ICallbackHandler, IDisposable
	{
		public interface ICallbackHandler : ISynchronizeInvoke
		{
			void OnLearningChanged (Bridge bridge, uint portIndex, uint treeIndex, bool enabled);
			void OnForwardingChanged (Bridge bridge, uint portIndex, uint treeIndex, bool enabled);
			void OnBpduTransmit (Bridge bridge, uint portIndex, byte[] bpdu);
			void OnDebugStrOut (Bridge bridge, int portIndex, int treeIndex, string str);
		}

		struct ReceivedBpdu
		{
			public readonly byte [] Bpdu;
			public readonly uint PortIndex;
			public readonly uint Timestamp;

			public ReceivedBpdu (byte [] bpdu, uint portIndex, uint timestamp)
			{
				this.Bpdu = bpdu;
				this.PortIndex = portIndex;
				this.Timestamp = timestamp;
			}
		}

		static Random _random = new Random ();
		static Pen selectedBridgePen = new Pen (SystemColors.WindowText, 2);
		static Pen selectedPortPen = new Pen (SystemColors.WindowText, 2);

		readonly SheetArea _sheetArea;
		ICallbackHandler _callbackHandler;
		readonly EventWaitHandle _bridgeCreateEventHandle;
		readonly BridgeWrapper _wrapper;
		readonly object _wrapperLock;
		readonly int _uniqueId;
		Thread _thread;
		EventWaitHandle _bridgeDestroyEventHandle;
		readonly Port [] _ports;
		readonly ReadOnlyCollection<Port> _portsReadOnly;
		readonly BridgeTree[] _trees;
		readonly ReadOnlyCollection<BridgeTree> _treesReadOnly;
		bool _suspendedPeriodicProcessing;
		bool _disposed;

		ConcurrentQueue<ReceivedBpdu> _receiveQueue;
		Semaphore _receiveQueueSemaphore;

		Point _location;
		Size _size;
		string _name;

		public Bridge (SheetArea sheetArea, uint portCount, uint treeCount, StpVersion protocolVersion, ulong address, int uniqueId, string name)
		{
			_sheetArea = sheetArea;

			int w = Port.InitialEdgeToPortSpacing
				+ Port.InteriorDrawingLongSize * (int) portCount
				+ Port.InitialPortToPortSpacing * (int) (portCount - 1)
				+ Port.InitialEdgeToPortSpacing;
			int h = 140;

			_location = new Point (0, 0);
			_size = new Size (w, h);

			_uniqueId = uniqueId;

			_wrapperLock = new object ();
			_wrapper = new BridgeWrapper (portCount, treeCount, protocolVersion, address, 512, this);

			if (name != null)
			{
				if (name.IndexOfAny (Path.GetInvalidFileNameChars ()) != -1)
					throw new ArgumentException ("The bridge name must be a valid filename.", "name");

				_name = name;
			}

			// We create a dedicated thread for each bridge so as to have a better simulation of the real bridge.
			// This dedicated bridge thread handles incoming BPDUs, as these are asynchronous events on the real bridge.
			// Maybe in the future it will handle other bridge events as well.

			_receiveQueue = new ConcurrentQueue<ReceivedBpdu> ();
			_receiveQueueSemaphore = new Semaphore (0, int.MaxValue);
			_bridgeCreateEventHandle = new EventWaitHandle (false, EventResetMode.ManualReset);
			_bridgeDestroyEventHandle = new EventWaitHandle (false, EventResetMode.ManualReset);
			_thread = new Thread (BridgeThreadProc);
			_thread.Name = String.Format ("{0} Thread", name);
			_thread.Start ();
			_bridgeCreateEventHandle.WaitOne ();
			_bridgeCreateEventHandle.Dispose ();
			_bridgeCreateEventHandle = null;

			_trees = new BridgeTree [treeCount];
			for (uint treeIndex = 0; treeIndex < treeCount; treeIndex++)
				_trees [treeIndex] = new BridgeTree (this, treeIndex);

			_treesReadOnly = new ReadOnlyCollection<BridgeTree> (_trees);

			_ports = new Port [portCount];
			Debug.Assert ((Port.InteriorDrawingLongSize % 2) == 0);
			int offset = Port.InitialEdgeToPortSpacing + Port.InteriorDrawingLongSize / 2;
			for (uint portIndex = 0; portIndex < portCount; portIndex++)
			{
				_ports [portIndex] = new Port (this, portIndex, treeCount, Side.Bottom, offset);
				offset += (Port.InteriorDrawingLongSize + Port.InitialPortToPortSpacing);
			}

			_portsReadOnly = new ReadOnlyCollection<Port> (_ports);
		}
		
		public void Dispose ()
		{
			if (!_disposed)
			{
				_bridgeDestroyEventHandle.Set ();
				_thread.Join ();
				_bridgeDestroyEventHandle.Dispose ();
				_bridgeDestroyEventHandle = null;

				_receiveQueueSemaphore.Dispose ();

				_wrapper.Dispose ();

				_disposed = true;
			}
		}

		void BridgeThreadProc ()
		{
			Timer oneSecondTimer = null;
			EventWaitHandle oneSecondTimerEvent = null;

			Timer pollTimer = null;
			EventWaitHandle pollTimerEvent = null;

			try
			{
				Monitor.Enter (_random);
				int oneSecondPeriod = 900 + _random.Next (200);
				int pollPeriod = 90 + _random.Next (20);
				Monitor.Exit (_random);

				oneSecondTimerEvent = new EventWaitHandle (false, EventResetMode.AutoReset);
				oneSecondTimer = new Timer (_ => oneSecondTimerEvent.Set (), null, oneSecondPeriod, oneSecondPeriod);

				pollTimerEvent = new EventWaitHandle (false, EventResetMode.AutoReset);
				pollTimer = new Timer (_ => pollTimerEvent.Set (), null, pollPeriod, pollPeriod);

				_bridgeCreateEventHandle.Set ();

				WaitHandle [] handles = new WaitHandle []
				{
					_bridgeDestroyEventHandle,
					_receiveQueueSemaphore,
					oneSecondTimerEvent,
					pollTimerEvent,
				};

				while (true)
				{
					int waitResult = WaitHandle.WaitAny (handles);

					uint timestamp = (uint) Environment.TickCount;

					lock (_wrapperLock)
					{
						#region _bridgeDestroyEventHandle
						if (handles [waitResult] == _bridgeDestroyEventHandle)
						{
							break;
						}
						#endregion
						#region pollTimerEvent
						else if (handles [waitResult] == pollTimerEvent)
						{
							bool needInvalidate = false;
							if (!_powered)
							{
								if (_wrapper.IsStarted ())
								{
									_wrapper.Stop (timestamp);
									needInvalidate = true;
								}

								foreach (var port in _ports)
								{
									port.MAC_Operational = false;

									if (_wrapper.GetPortEnabled (port.PortIndex))
									{
										_wrapper.OnPortDisabled (port.PortIndex, timestamp);
										needInvalidate = true;
									}
								}
							}
							else
							{
								foreach (var port in _ports)
								{
									if (port.MAC_Operational == false)
									{
										if (_wrapper.GetPortEnabled (port.PortIndex))
										{
											_wrapper.OnPortDisabled (port.PortIndex, timestamp);
											needInvalidate = true;
										}
									}
									else
									{
										if (_wrapper.GetPortEnabled (port.PortIndex) == false)
										{
											_wrapper.OnPortEnabled (port.PortIndex, 100, true, timestamp);
											needInvalidate = true;
										}
									}
								}
							}

							if (needInvalidate)
								SafeInvalidate ();
						}
						#endregion
						#region _receiveQueueSemaphore
						else if (handles [waitResult] == _receiveQueueSemaphore)
						{
							ReceivedBpdu bpdu;
							_receiveQueue.TryDequeue (out bpdu);
							if (_powered)
							{
								// Let force MAC_Operational to true and enable the port if not already enabled.
								_ports [bpdu.PortIndex].MAC_Operational = true;

								if (_wrapper.GetPortEnabled (bpdu.PortIndex) == false)
								{
									// Note: We really should read the speed and duplex information from the wire somehow.
									_wrapper.OnPortEnabled (bpdu.PortIndex, 100, true, timestamp);
									SafeInvalidate ();
								}

								_wrapper.OnBpduReceived (bpdu.PortIndex, bpdu.Bpdu, bpdu.Timestamp);
							}
							else
							{
								// A bridge received a BPDU while powered off.
								// This scenario is intentionally allowed and the bpdu discarded, as it may occur also for a real bridge.
							}
						}
						#endregion
						#region oneSecondTimerEvent
						else if (handles [waitResult] == oneSecondTimerEvent)
						{
							if (_suspendedPeriodicProcessing == false)
								_wrapper.OnOneSecondTick ((uint) Environment.TickCount);
						}
						#endregion
						else
							Debug.Assert (false);
					}
				}
			}
			finally
			{
				if (pollTimer != null)
					pollTimer.Dispose ();

				if (pollTimerEvent != null)
					pollTimerEvent.Dispose ();

				if (oneSecondTimer != null)
					oneSecondTimer.Dispose ();

				if (oneSecondTimerEvent != null)
					oneSecondTimerEvent.Dispose ();
			}
		}

		[Browsable (false)]
		public Point Location 
		{
			[DebuggerStepThrough]
			get { return _location; }

			[DebuggerStepThrough]
			set { _location = value; } 
		}

		[Browsable (false)]
		public int X
		{
			[DebuggerStepThrough]
			get { return _location.X; }
			
			[DebuggerStepThrough]
			set { _location = new Point (value, _location.Y); }
		}

		[Browsable (false)]
		public int Y
		{
			[DebuggerStepThrough]
			get { return _location.Y; }

			[DebuggerStepThrough]
			set { _location = new Point (_location.X, value); }
		}

		[Browsable (false)]
		public Size Size
		{
			[DebuggerStepThrough]
			get { return _size; }
		}

		[Browsable (false)]
		public int Width
		{
			[DebuggerStepThrough]
			get { return _size.Width; }

			[DebuggerStepThrough]
			set { _size = new Size (value, _size.Height); }
		}

		[Browsable (false)]
		public int Height
		{
			[DebuggerStepThrough] 
			get { return _size.Height; }

			[DebuggerStepThrough] 
			set { _size = new Size (_size.Width, value); }
		}

		[Browsable (false)]
		public Rectangle Bounds
		{
			[DebuggerStepThrough]
			get { return new Rectangle (_location, _size); }
		}

		[Browsable (false)]
		public ReadOnlyCollection<Port> Ports 
		{
			[DebuggerStepThrough]
			get { return _portsReadOnly; } 
		}

		[Browsable (false)]
		public ReadOnlyCollection<BridgeTree> Trees
		{
			[DebuggerStepThrough]
			get { return _treesReadOnly; } 
		}

		[Browsable (false)]
		public BridgeWrapper Wrapper
		{
			[DebuggerStepThrough]
			get { return _wrapper; }
		}

		[Browsable (false)]
		public object WrapperLock
		{
			[DebuggerStepThrough]
			get { return _wrapperLock; }
		}

		public void SetCoordsForInteriorPort (Port _port, int mouseX, int mouseY)
		{
			mouseX -= _location.X;
			mouseY -= _location.Y;

			Debug.Assert ((Port.InteriorDrawingLongSize % 2) == 0);

			float wh = (float) _size.Width / (float) _size.Height;

			// top side
			if ((mouseX > mouseY * wh) && (_size.Width - mouseX) > mouseY * wh)
			{
				_port.Side = Side.Top;

				if (mouseX < Port.InteriorDrawingLongSize / 2)
					_port.Offset = Port.InteriorDrawingLongSize / 2;
				else if (mouseX > _size.Width - Port.InteriorDrawingLongSize / 2)
					_port.Offset = _size.Width - Port.InteriorDrawingLongSize / 2;
				else
					_port.Offset = mouseX;
			}

			// bottom side
			else if ((mouseX <= mouseY * wh) && (_size.Width - mouseX) <= mouseY * wh)
			{
				_port.Side = Side.Bottom;

				if (mouseX < Port.InteriorDrawingLongSize / 2 + 1)
					_port.Offset = Port.InteriorDrawingLongSize / 2 + 1;
				else if (mouseX > _size.Width - Port.InteriorDrawingLongSize / 2)
					_port.Offset = _size.Width - Port.InteriorDrawingLongSize / 2;
				else
					_port.Offset = mouseX;
			}

			// left side
			if ((mouseX <= mouseY * wh) && (_size.Width - mouseX) > mouseY * wh)
			{
				_port.Side = Side.Left;

				if (mouseY < Port.InteriorDrawingLongSize / 2)
					_port.Offset = Port.InteriorDrawingLongSize / 2;
				else if (mouseY > _size.Height - Port.InteriorDrawingLongSize / 2)
					_port.Offset = _size.Height - Port.InteriorDrawingLongSize / 2;
				else
					_port.Offset = mouseY;
			}

			// right side
			if ((mouseX > mouseY * wh) && (_size.Width - mouseX) <= mouseY * wh)
			{
				_port.Side = Side.Right;

				if (mouseY < Port.InteriorDrawingLongSize / 2)
					_port.Offset = Port.InteriorDrawingLongSize / 2;
				else if (mouseY > _size.Height - Port.InteriorDrawingLongSize / 2)
					_port.Offset = _size.Height - Port.InteriorDrawingLongSize / 2;
				else
					_port.Offset = mouseY;
			}
		}
		
		public const int GripSize = 20;

		// TODO: change vlan number to int
		public void Paint (Graphics g, bool selected, int selectedPortIndex, ushort selectedVlanNumber)
		{
			// bridge rectangle
			if (selected && (selectedPortIndex == -1))
				// bridge is selected - thick border
				g.DrawRectangle (selectedBridgePen, _location.X + 1, _location.Y + 1, _size.Width - 1, _size.Height - 1);
			else
				// bridge not selected - thin border
				g.DrawRectangle (SystemPens.WindowText, _location.X, _location.Y, _size.Width, _size.Height);

			// no need to lock it when reading from the interface thread
			byte treeIndex = _wrapper.GetTreeIndexFromVlanNumber (selectedVlanNumber);

			// ---------------------------------------------
			// draw text

			StringBuilder sb = new StringBuilder ();
			sb.AppendFormat ("{0}\r\n", _name);
			sb.AppendFormat ("Showing VLAN{0}", selectedVlanNumber);
			if (ForceProtocolVersion == StpVersion.MSTP)
			{
				if (treeIndex == 0)
					sb.Append (" (CIST)");
				else
					sb.AppendFormat (" (MSTI #{0})", treeIndex);
			}
			sb.AppendLine ();

			g.DrawString (sb.ToString (), SystemFonts.StatusFont, SystemBrushes.WindowText, this.Location.X + 10, this.Location.Y + 15);

			// ---------------------------------------------
			// ports
			for (uint portIndex = 0; portIndex < _ports.Length; portIndex++)
			{
				Port port = _ports [portIndex];

				// interior port rectangle
				Pen portRectanglePen;
				if (selected && (portIndex == selectedPortIndex))
					portRectanglePen = selectedPortPen;
				else
					portRectanglePen = SystemPens.WindowText;

				// Draw the interior of the port.
				RectangleF interiorRect = port.GetInteriorRect ();
				if (selected && selectedPortIndex == portIndex)
				{
					interiorRect.X++; interiorRect.Width--;
					interiorRect.Y++; interiorRect.Height--;
				}
				g.DrawRectangle (portRectanglePen, interiorRect.X, interiorRect.Y, interiorRect.Width, interiorRect.Height);

				// Draw the exterior of the port.
				Bitmap bitmap;
				switch (port.Side)
				{
					case Side.Bottom:
						bitmap = port.GetBitmap (treeIndex, RotateFlipType.RotateNoneFlipNone);
						Debug.Assert (((bitmap.Width % 2) == 0) && ((bitmap.Height % 2) == 0));
						g.DrawImage (bitmap, _location.X + port.Offset - bitmap.Width / 2, _location.Y + _size.Height);
						break;

					case Side.Left:
						bitmap = port.GetBitmap (treeIndex, RotateFlipType.Rotate90FlipNone);
						Debug.Assert (((bitmap.Width % 2) == 0) && ((bitmap.Height % 2) == 0));
						g.DrawImage (bitmap, _location.X - Port.ExteriorDrawingHeight, _location.Y + port.Offset - bitmap.Height / 2);
						break;

					case Side.Top:
						bitmap = port.GetBitmap (treeIndex, RotateFlipType.Rotate180FlipNone);
						Debug.Assert (((bitmap.Width % 2) == 0) && ((bitmap.Height % 2) == 0));
						g.DrawImage (bitmap, _location.X + port.Offset - bitmap.Width / 2, _location.Y - Port.ExteriorDrawingHeight);
						break;

					case Side.Right:
						bitmap = port.GetBitmap (treeIndex, RotateFlipType.Rotate270FlipNone);
						Debug.Assert (((bitmap.Width % 2) == 0) && ((bitmap.Height % 2) == 0));
						g.DrawImage (bitmap, _location.X + _size.Width, _location.Y + port.Offset - bitmap.Height / 2);
						break;
				}

				// fill the gray/green circle representing the operational state of the port.
				Brush b = port.MAC_Operational ? Brushes.Green : Brushes.DarkGray;
				g.FillEllipse (b, interiorRect.X + 2, interiorRect.Y + 2, (float) 10, (float) 10);
			}

			// resize grips
			for (int i = 3; i < GripSize; i += 3)
			{
				g.DrawLine (SystemPens.WindowText, _location.X, _location.Y + i, _location.X + i, _location.Y);
				g.DrawLine (SystemPens.WindowText, _location.X + _size.Width - i, _location.Y, _location.X + _size.Width, _location.Y + i);
				g.DrawLine (SystemPens.WindowText, _location.X, _location.Y + _size.Height - i, _location.X + i, _location.Y + _size.Height);
				g.DrawLine (SystemPens.WindowText, _location.X + _size.Width - i, _location.Y + _size.Height, _location.X + _size.Width, _location.Y + _size.Height - i);
			}

			// fill the gray/green circle representing the power state of the bridge.
			Brush brush = _powered ? Brushes.Green : Brushes.DarkGray;
			g.FillEllipse (brush, _location.X + 2, _location.Y + 2, (float) 15, (float) 15);
		}

		public void EnqueueReceivedBpdu (uint portIndex, byte [] bpdu, uint timestamp)
		{
			var receivedBpdu = new ReceivedBpdu (bpdu, portIndex, timestamp);
			_receiveQueue.Enqueue (receivedBpdu);
			_receiveQueueSemaphore.Release ();
		}

		[Description ("Port Count")]
		public uint PortCount { get { return _wrapper.PortCount; } }

		[Description ("Tree Count")]
		public uint TreeCount { get { return _wrapper.TreeCount; } }

		[TypeConverter (typeof (BridgeAddressTypeConverter))]
		[Description ("Address")]
		public UInt64 Address
		{
			get { return _wrapper.GetBridgeAddress (); }
			set
			{
				lock (_wrapperLock)
					_wrapper.SetBridgeAddress (value); 
			}
		}

		[Description ("ForceProtocolVersion")]
		public StpVersion ForceProtocolVersion
		{
			get { return _wrapper.GetStpVersion (); }
			set 
			{
				throw new NotImplementedException ("Changing the STP version for an existing bridge is currently not implement in the MSTP library. Try creating another bridge.");
				//lock (_wrapperLock)
				//	_wrapper_.SetStpVersion (value); 
			}
		}

		[Description ("Number of bytes allocated by the STP library. This varies slightly for other compilers and platforms.")]
		public uint BytesAllocated
		{
			get { return _wrapper.BytesAllocated; }
		}

		[Browsable (false)]
		public ICallbackHandler CallbackHandler
		{
			get { return _callbackHandler; }
			set { _callbackHandler = value; }
		}

		// We use this instead of holding references to Bridge objects, so the garbage collector can free the memory when they're deleted.
		// (in the logger for instance)
		public int UniqueID
		{
			get { return _uniqueId; }
		}

		[Browsable (true)]
		public string Name
		{
			get { return _name; }
			set 
			{
				if (_name != value)
				{
					_name = value;
					SafeInvalidate ();
				}
			}
		}

		public MstConfigId MstConfigId
		{
			get { return new MstConfigId (this); }
		}

		#region ICallbackHandler Members

		void BridgeWrapper.ICallbackHandler.DebugStrOut (BridgeWrapper bridge, int portIndex, int treeIndex, string str, bool flush)
		{
			_callbackHandler.OnDebugStrOut (this, portIndex, treeIndex, str);
		}

		void BridgeWrapper.ICallbackHandler.EnableForwarding (BridgeWrapper bridge, uint portIndex, uint treeIndex, bool enable)
		{
			_callbackHandler.OnForwardingChanged (this, portIndex, treeIndex, enable);
		}

		void BridgeWrapper.ICallbackHandler.EnableLearning (BridgeWrapper bridge, uint portIndex, uint treeIndex, bool enable)
		{
			_callbackHandler.OnLearningChanged (this, portIndex, treeIndex, enable);
		}

		void BridgeWrapper.ICallbackHandler.FdbFlush (BridgeWrapper bridge, uint portIndex, uint treeIndex, FlushFdbType flushType)
		{
		}

		void BridgeWrapper.ICallbackHandler.Transmit (BridgeWrapper bridge, uint portIndex, byte [] bpdu)
		{
			_callbackHandler.OnBpduTransmit (this, portIndex, bpdu);
		}

		#endregion

		public void SuspendPeriodicProcessing ()
		{
			_suspendedPeriodicProcessing = true;
		}

		public void ResumePeriodicProcessing ()
		{
			_suspendedPeriodicProcessing = false;
		}

		bool _powered;

		/// <summary>
		/// This is written by the user interface code, and read by the code in Bridge.BridgeThreadProc and SheetArea.PortCheckThreadProc.
		/// </summary>
		public bool Powered
		{
			get { return _powered; }
			set
			{
				if (_powered != value)
				{
					_powered = value;

					// We only start or stop the bridge. The code in SheetArea.PortCheckThreadProc() will update MAC_Operational as necessary.

					uint timestamp = (uint) Environment.TickCount;
					lock (_wrapperLock)
					{
						if (_powered)
							_wrapper.Start (timestamp);
						else
							_wrapper.Stop (timestamp);
					}

					SafeInvalidate ();
				}
			}
		}

		public void SafeInvalidate ()
		{
			_sheetArea.SafeInvalidate ();
		}

		public override string ToString ()
		{
			return _name;
		}
	}
}
